#include "wx/defs.h"
#include <assert.h>
#include <sys/types.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <ctype.h>
#include <signal.h>

#define INVALID_SOCKET -1

extern "C" { typedef void ( *wxSigHandler )( int ); }

#define MASK_SIGNAL()                       \
  {                                           \
    wxSigHandler old_handler = signal(SIGPIPE, SIG_IGN);

#define UNMASK_SIGNAL()                     \
  signal(SIGPIPE, old_handler);           \
  }

#  define GSOCKET_MSG_NOSIGNAL MSG_NOSIGNAL
#  include "wx/unix/gsockunx.h"
#  include "wx/unix/private.h"
#  include "wx/gsocket.h"

struct hostent * wxGethostbyname_r( const char *hostname, struct hostent *h, void *buffer, int size, int *err ) {
  struct hostent *he = NULL;
  *err = 0;
  if( gethostbyname_r( hostname, h, ( char* )buffer, size, &he, err ) ) {
    he = NULL;
  }
  return he;
}

struct hostent * wxGethostbyaddr_r( const char *addr_buf, int buf_size, int proto, struct hostent *h,
                                    void *buffer, int size, int *err ) {
  struct hostent *he = NULL;
  *err = 0;
  if( gethostbyaddr_r( addr_buf, buf_size, proto, h, ( char* )buffer, size, &he, err ) ) {
    he = NULL;
  }
  return he;
}

struct servent *wxGetservbyname_r( const char *port, const char *protocol, struct servent *serv, void *buffer, int size ) {
  struct servent *se = NULL;
  if( getservbyname_r( port, protocol, serv, ( char* )buffer, size, &se ) ) {
    se = NULL;
  }
  return se;
}

#  define GSocket_Debug(args)

static GSocketGUIFunctionsTable *gs_gui_functions;

class GSocketGUIFunctionsTableNull: public GSocketGUIFunctionsTable {
  public:
    virtual bool OnInit();
    virtual void OnExit();
    virtual bool CanUseEventLoop();
    virtual bool Init_Socket( GSocket *socket );
    virtual void Destroy_Socket( GSocket *socket );
    virtual void Install_Callback( GSocket *socket, GSocketEvent event );
    virtual void Uninstall_Callback( GSocket *socket, GSocketEvent event );
    virtual void Enable_Events( GSocket *socket );
    virtual void Disable_Events( GSocket *socket );
};

bool GSocketGUIFunctionsTableNull::OnInit() { return true; }
void GSocketGUIFunctionsTableNull::OnExit() {}
bool GSocketGUIFunctionsTableNull::CanUseEventLoop() { return false; }
bool GSocketGUIFunctionsTableNull::Init_Socket( GSocket * ( socket ) ) { return true; }
void GSocketGUIFunctionsTableNull::Destroy_Socket( GSocket * ( socket ) ) {}
void GSocketGUIFunctionsTableNull::Install_Callback( GSocket * ( socket ), GSocketEvent( event ) ) {}
void GSocketGUIFunctionsTableNull::Uninstall_Callback( GSocket * ( socket ), GSocketEvent( event ) ) {}
void GSocketGUIFunctionsTableNull::Enable_Events( GSocket * ( socket ) ) {}
void GSocketGUIFunctionsTableNull::Disable_Events( GSocket * ( socket ) ) {}

void GSocket_SetGUIFunctions( GSocketGUIFunctionsTable *guifunc ) {
  gs_gui_functions = guifunc;
}

int GSocket_Init( void ) {
  if( !gs_gui_functions ) {
    static GSocketGUIFunctionsTableNull table;
    gs_gui_functions = &table;
  }
  if( !gs_gui_functions->OnInit() ) {
    return 0;
  }
  return 1;
}

void GSocket_Cleanup( void ) {
  if( gs_gui_functions ) {
    gs_gui_functions->OnExit();
  }
}

GSocket::GSocket() {
  int i;
  m_fd                  = INVALID_SOCKET;
  for( i = 0; i < GSOCK_MAX_EVENT; i++ ) {
    m_cbacks[i]         = NULL;
  }
  m_detected            = 0;
  m_local               = NULL;
  m_peer                = NULL;
  m_error               = GSOCK_NOERROR;
  m_server              = false;
  m_stream              = true;
  m_gui_dependent       = NULL;
  m_non_blocking        = false;
  m_reusable            = false;
  m_timeout             = 10 * 60 * 1000;
  m_establishing        = false;
  assert( gs_gui_functions );
  m_ok = gs_gui_functions->Init_Socket( this );
}

void GSocket::Close() {
  gs_gui_functions->Disable_Events( this );
  if( m_fd != INVALID_SOCKET ) {
    close( m_fd );
  }
  m_fd = INVALID_SOCKET;
}

GSocket::~GSocket() {
  assert( this );
  if( m_fd != INVALID_SOCKET ) {
    Shutdown();
  }
  gs_gui_functions->Destroy_Socket( this );
  if( m_local ) {
    GAddress_destroy( m_local );
  }
  if( m_peer ) {
    GAddress_destroy( m_peer );
  }
}

void GSocket::Shutdown() {
  int evt;
  assert( this );
  gs_gui_functions->Disable_Events( this );
  if( m_fd != INVALID_SOCKET ) {
    shutdown( m_fd, 1 );
    Close();
  }
  for( evt = 0; evt < GSOCK_MAX_EVENT; evt++ ) {
    m_cbacks[evt] = NULL;
  }
  m_detected = GSOCK_LOST_FLAG;
}

GSocketError GSocket::SetLocal( GAddress *address ) {
  assert( this );
  if( ( m_fd != INVALID_SOCKET && !m_server ) ) {
    m_error = GSOCK_INVSOCK;
    return GSOCK_INVSOCK;
  }
  if( address == NULL || address->m_family == GSOCK_NOFAMILY ) {
    m_error = GSOCK_INVADDR;
    return GSOCK_INVADDR;
  }
  if( m_local ) {
    GAddress_destroy( m_local );
  }
  m_local = GAddress_copy( address );
  return GSOCK_NOERROR;
}

GSocketError GSocket::SetPeer( GAddress *address ) {
  assert( this );
  if( address == NULL || address->m_family == GSOCK_NOFAMILY ) {
    m_error = GSOCK_INVADDR;
    return GSOCK_INVADDR;
  }
  if( m_peer ) {
    GAddress_destroy( m_peer );
  }
  m_peer = GAddress_copy( address );
  return GSOCK_NOERROR;
}

GAddress *GSocket::GetLocal() {
  GAddress *address;
  struct sockaddr addr;
  WX_SOCKLEN_T size = sizeof( addr );
  GSocketError err;
  assert( this );
  if( m_local ) {
    return GAddress_copy( m_local );
  }
  if( m_fd == INVALID_SOCKET ) {
    m_error = GSOCK_INVSOCK;
    return NULL;
  }
  if( getsockname( m_fd, &addr, ( WX_SOCKLEN_T * ) &size ) < 0 ) {
    m_error = GSOCK_IOERR;
    return NULL;
  }
  address = GAddress_new();
  if( address == NULL ) {
    m_error = GSOCK_MEMERR;
    return NULL;
  }
  err = _GAddress_translate_from( address, &addr, size );
  if( err != GSOCK_NOERROR ) {
    GAddress_destroy( address );
    m_error = err;
    return NULL;
  }
  return address;
}

GAddress *GSocket::GetPeer() {
  assert( this );
  if( m_peer ) {
    return GAddress_copy( m_peer );
  }
  return NULL;
}

GSocketError GSocket::SetServer() {
  int arg = 1;
  assert( this );
  if( m_fd != INVALID_SOCKET ) {
    m_error = GSOCK_INVSOCK;
    return GSOCK_INVSOCK;
  }
  if( !m_local ) {
    m_error = GSOCK_INVADDR;
    return GSOCK_INVADDR;
  }
  m_stream   = true;
  m_server   = true;
  m_fd = socket( m_local->m_realfamily, SOCK_STREAM, 0 );
  if( m_fd == INVALID_SOCKET ) {
    m_error = GSOCK_IOERR;
    return GSOCK_IOERR;
  }
  ioctl( m_fd, FIONBIO, &arg );
  gs_gui_functions->Enable_Events( this );
  if( m_reusable ) {
    setsockopt( m_fd, SOL_SOCKET, SO_REUSEADDR, ( const char* )&arg, sizeof( arg ) );
    setsockopt( m_fd, SOL_SOCKET, SO_REUSEPORT, ( const char* )&arg, sizeof( arg ) );
  }
  if( ( bind( m_fd, m_local->m_addr, m_local->m_len ) != 0 ) ||
      ( getsockname( m_fd, m_local->m_addr, ( WX_SOCKLEN_T * ) &m_local->m_len ) != 0 ) ||
      ( listen( m_fd, 5 ) != 0 ) ) {
    Close();
    m_error = GSOCK_IOERR;
    return GSOCK_IOERR;
  }
  return GSOCK_NOERROR;
}

GSocket *GSocket::WaitConnection() {
  struct sockaddr from;
  WX_SOCKLEN_T fromlen = sizeof( from );
  GSocket *connection;
  GSocketError err;
  int arg = 1;
  assert( this );
  if( m_fd == INVALID_SOCKET || !m_server ) {
    m_error = GSOCK_INVSOCK;
    return NULL;
  }
  connection = GSocket_new();
  if( !connection ) {
    m_error = GSOCK_MEMERR;
    return NULL;
  }
  if( Input_Timeout() == GSOCK_TIMEDOUT ) {
    delete connection;
    return NULL;
  }
  connection->m_fd = accept( m_fd, &from, ( WX_SOCKLEN_T * ) &fromlen );
  Enable( GSOCK_CONNECTION );
  if( connection->m_fd == INVALID_SOCKET ) {
    if( errno == EWOULDBLOCK ) {
      m_error = GSOCK_WOULDBLOCK;
    } else {
      m_error = GSOCK_IOERR;
    }
    delete connection;
    return NULL;
  }
  connection->m_server   = false;
  connection->m_stream   = true;
  connection->m_peer = GAddress_new();
  if( !connection->m_peer ) {
    delete connection;
    m_error = GSOCK_MEMERR;
    return NULL;
  }
  err = _GAddress_translate_from( connection->m_peer, &from, fromlen );
  if( err != GSOCK_NOERROR ) {
    delete connection;
    m_error = err;
    return NULL;
  }
  ioctl( connection->m_fd, FIONBIO, &arg );
  gs_gui_functions->Enable_Events( connection );
  return connection;
}

bool GSocket::SetReusable() {
  if( this && m_fd == INVALID_SOCKET ) {
    m_reusable = true;
    return true;
  }
  return false;
}

GSocketError GSocket::Connect( GSocketStream stream ) {
  int err, ret;
  int arg = 1;
  assert( this );
  Enable( GSOCK_CONNECTION );
  if( m_fd != INVALID_SOCKET ) {
    m_error = GSOCK_INVSOCK;
    return GSOCK_INVSOCK;
  }
  if( !m_peer ) {
    m_error = GSOCK_INVADDR;
    return GSOCK_INVADDR;
  }
  m_stream   = ( stream == GSOCK_STREAMED );
  m_server   = false;
  m_establishing = false;
  m_fd = socket( m_peer->m_realfamily, m_stream ? SOCK_STREAM : SOCK_DGRAM, 0 );
  if( m_fd == INVALID_SOCKET ) {
    m_error = GSOCK_IOERR;
    return GSOCK_IOERR;
  }
  ioctl( m_fd, FIONBIO, &arg );
  if( m_reusable ) {
    setsockopt( m_fd, SOL_SOCKET, SO_REUSEADDR, ( const char* )&arg, sizeof( arg ) );
  }
  if( m_local && m_local->m_addr ) {
    if( bind( m_fd, m_local->m_addr, m_local->m_len ) < 0 ) {
      Close();
      m_error = GSOCK_IOERR;
      return GSOCK_IOERR;
    }
  }
  ret = connect( m_fd, m_peer->m_addr, m_peer->m_len );
  if( m_non_blocking || ret == 0 ) {
    gs_gui_functions->Enable_Events( this );
  }
  if( ret == -1 ) {
    err = errno;
    if( ( err == EINPROGRESS ) && ( !m_non_blocking ) ) {
      if( Output_Timeout() == GSOCK_TIMEDOUT ) {
        Close();
        return GSOCK_TIMEDOUT;
      } else {
        int error;
        SOCKOPTLEN_T len = sizeof( error );
        getsockopt( m_fd, SOL_SOCKET, SO_ERROR, ( char* ) &error, &len );
        gs_gui_functions->Enable_Events( this );
        if( !error ) {
          return GSOCK_NOERROR;
        }
      }
    }
    if( ( err == EINPROGRESS ) && ( m_non_blocking ) ) {
      m_establishing = true;
      m_error = GSOCK_WOULDBLOCK;
      return GSOCK_WOULDBLOCK;
    }
    Close();
    m_error = GSOCK_IOERR;
    return GSOCK_IOERR;
  }
  return GSOCK_NOERROR;
}

GSocketError GSocket::SetNonOriented() {
  int arg = 1;
  assert( this );
  if( m_fd != INVALID_SOCKET ) {
    m_error = GSOCK_INVSOCK;
    return GSOCK_INVSOCK;
  }
  if( !m_local ) {
    m_error = GSOCK_INVADDR;
    return GSOCK_INVADDR;
  }
  m_stream   = false;
  m_server   = false;
  m_fd = socket( m_local->m_realfamily, SOCK_DGRAM, 0 );
  if( m_fd == INVALID_SOCKET ) {
    m_error = GSOCK_IOERR;
    return GSOCK_IOERR;
  }
  ioctl( m_fd, FIONBIO, &arg );
  gs_gui_functions->Enable_Events( this );
  if( m_reusable ) {
    setsockopt( m_fd, SOL_SOCKET, SO_REUSEADDR, ( const char* )&arg, sizeof( arg ) );
  }
  if( ( bind( m_fd, m_local->m_addr, m_local->m_len ) != 0 ) ||
      ( getsockname( m_fd, m_local->m_addr, ( WX_SOCKLEN_T * ) &m_local->m_len ) != 0 ) ) {
    Close();
    m_error = GSOCK_IOERR;
    return GSOCK_IOERR;
  }
  return GSOCK_NOERROR;
}

int GSocket::Read( char *buffer, int size ) {
  int ret;
  assert( this );
  if( m_fd == INVALID_SOCKET || m_server ) {
    m_error = GSOCK_INVSOCK;
    return -1;
  }
  Disable( GSOCK_INPUT );
  if( Input_Timeout() == GSOCK_TIMEDOUT ) {
    m_error = GSOCK_TIMEDOUT;
    ret = -1;
  } else {
    if( m_stream ) {
      ret = Recv_Stream( buffer, size );
    } else {
      ret = Recv_Dgram( buffer, size );
    }
    if( ( ret == 0 ) && m_stream ) {
      m_detected = GSOCK_LOST_FLAG;
      Detected_Read();
      return 0;
    } else if( ret == -1 ) {
      if( ( errno == EWOULDBLOCK ) || ( errno == EAGAIN ) ) {
        m_error = GSOCK_WOULDBLOCK;
      } else {
        m_error = GSOCK_IOERR;
      }
    }
  }
  Enable( GSOCK_INPUT );
  return ret;
}

int GSocket::Write( const char *buffer, int size ) {
  int ret;
  assert( this );
  GSocket_Debug( ( "GSocket_Write #1, size %d\n", size ) );
  if( m_fd == INVALID_SOCKET || m_server ) {
    m_error = GSOCK_INVSOCK;
    return -1;
  }
  GSocket_Debug( ( "GSocket_Write #2, size %d\n", size ) );
  if( Output_Timeout() == GSOCK_TIMEDOUT ) {
    return -1;
  }
  GSocket_Debug( ( "GSocket_Write #3, size %d\n", size ) );
  if( m_stream ) {
    ret = Send_Stream( buffer, size );
  } else
  { ret = Send_Dgram( buffer, size ); }
  GSocket_Debug( ( "GSocket_Write #4, size %d\n", size ) );
  if( ret == -1 ) {
    if( ( errno == EWOULDBLOCK ) || ( errno == EAGAIN ) ) {
      m_error = GSOCK_WOULDBLOCK;
      GSocket_Debug( ( "GSocket_Write error WOULDBLOCK\n" ) );
    } else {
      m_error = GSOCK_IOERR;
      GSocket_Debug( ( "GSocket_Write error IOERR\n" ) );
    }
    Enable( GSOCK_OUTPUT );
    return -1;
  }
  GSocket_Debug( ( "GSocket_Write #5, size %d ret %d\n", size, ret ) );
  return ret;
}

GSocketEventFlags GSocket::Select( GSocketEventFlags flags ) {
  if( !gs_gui_functions->CanUseEventLoop() ) {
    GSocketEventFlags result = 0;
    fd_set readfds;
    fd_set writefds;
    fd_set exceptfds;
    struct timeval tv;
    assert( this );
    if( m_fd == -1 ) {
      return ( GSOCK_LOST_FLAG & flags );
    }
    tv.tv_sec = m_timeout / 1000;
    tv.tv_usec = ( m_timeout % 1000 ) * 1000;
    wxFD_ZERO( &readfds );
    wxFD_ZERO( &writefds );
    wxFD_ZERO( &exceptfds );
    wxFD_SET( m_fd, &readfds );
    if( flags & GSOCK_OUTPUT_FLAG || flags & GSOCK_CONNECTION_FLAG ) {
      wxFD_SET( m_fd, &writefds );
    }
    wxFD_SET( m_fd, &exceptfds );
    result |= ( GSOCK_CONNECTION_FLAG & m_detected );
    if( ( m_detected & GSOCK_LOST_FLAG ) != 0 ) {
      m_establishing = false;
      return ( GSOCK_LOST_FLAG & flags );
    }
    if( select( m_fd + 1, &readfds, &writefds, &exceptfds, &tv ) <= 0 ) {
      return ( result & flags );
    }
    if( wxFD_ISSET( m_fd, &exceptfds ) ) {
      m_establishing = false;
      m_detected = GSOCK_LOST_FLAG;
      return ( GSOCK_LOST_FLAG & flags );
    }
    if( wxFD_ISSET( m_fd, &readfds ) ) {
      result |= GSOCK_INPUT_FLAG;
      if( m_server && m_stream ) {
        result |= GSOCK_CONNECTION_FLAG;
        m_detected |= GSOCK_CONNECTION_FLAG;
      }
    }
    if( wxFD_ISSET( m_fd, &writefds ) ) {
      if( m_establishing && !m_server ) {
        int error;
        SOCKOPTLEN_T len = sizeof( error );
        m_establishing = false;
        getsockopt( m_fd, SOL_SOCKET, SO_ERROR, ( char* )&error, &len );
        if( error ) {
          m_detected = GSOCK_LOST_FLAG;
          return ( GSOCK_LOST_FLAG & flags );
        } else {
          result |= GSOCK_CONNECTION_FLAG;
          m_detected |= GSOCK_CONNECTION_FLAG;
        }
      } else {
        result |= GSOCK_OUTPUT_FLAG;
      }
    }
    return ( result & flags );
  } else {
    assert( this );
    return flags & m_detected;
  }
}

void GSocket::SetNonBlocking( bool non_block ) {
  assert( this );
  GSocket_Debug( ( "GSocket_SetNonBlocking: %d\n", ( int )non_block ) );
  m_non_blocking = non_block;
}

void GSocket::SetTimeout( unsigned long millisec ) {
  assert( this );
  m_timeout = millisec;
}

GSocketError GSocket::GetError() {
  assert( this );
  return m_error;
}

void GSocket::SetCallback( GSocketEventFlags flags, GSocketCallback callback, char *cdata ) {
  int count;
  assert( this );
  for( count = 0; count < GSOCK_MAX_EVENT; count++ ) {
    if( ( flags & ( 1 << count ) ) != 0 ) {
      m_cbacks[count] = callback;
      m_data[count] = cdata;
    }
  }
}

void GSocket::UnsetCallback( GSocketEventFlags flags ) {
  int count;
  assert( this );
  for( count = 0; count < GSOCK_MAX_EVENT; count++ ) {
    if( ( flags & ( 1 << count ) ) != 0 ) {
      m_cbacks[count] = NULL;
      m_data[count] = NULL;
    }
  }
}

GSocketError GSocket::GetSockOpt( int level, int optname, void *optval, int *optlen ) {
  if( getsockopt( m_fd, level, optname, ( char* )optval, ( SOCKOPTLEN_T* )optlen ) == 0 ) {
    return GSOCK_NOERROR;
  }
  return GSOCK_OPTERR;
}

GSocketError GSocket::SetSockOpt( int level, int optname, const void *optval, int optlen ) {
  if( setsockopt( m_fd, level, optname, ( const char* )optval, optlen ) == 0 ) {
    return GSOCK_NOERROR;
  }
  return GSOCK_OPTERR;
}

#define CALL_CALLBACK(socket, event) {                                  \
    socket->Disable(event);                                               \
    if (socket->m_cbacks[event])                                          \
      socket->m_cbacks[event](socket, event, socket->m_data[event]);      \
  }


void GSocket::Enable( GSocketEvent event ) {
  m_detected &= ~( 1 << event );
  gs_gui_functions->Install_Callback( this, event );
}

void GSocket::Disable( GSocketEvent event ) {
  m_detected |= ( 1 << event );
  gs_gui_functions->Uninstall_Callback( this, event );
}

GSocketError GSocket::Input_Timeout() {
  struct timeval tv;
  fd_set readfds;
  int ret;
  tv.tv_sec  = ( m_timeout / 1000 );
  tv.tv_usec = ( m_timeout % 1000 ) * 1000;
  if( !m_non_blocking ) {
    wxFD_ZERO( &readfds );
    wxFD_SET( m_fd, &readfds );
    ret = select( m_fd + 1, &readfds, NULL, NULL, &tv );
    if( ret == 0 ) {
      GSocket_Debug( ( "GSocket_Input_Timeout, select returned 0\n" ) );
      m_error = GSOCK_TIMEDOUT;
      return GSOCK_TIMEDOUT;
    }
    if( ret == -1 ) {
      GSocket_Debug( ( "GSocket_Input_Timeout, select returned -1\n" ) );
      if( errno == EBADF ) {
        GSocket_Debug( ( "Invalid file descriptor\n" ) );
      }
      if( errno == EINTR ) {
        GSocket_Debug( ( "A non blocked signal was caught\n" ) );
      }
      if( errno == EINVAL ) {
        GSocket_Debug( ( "The highest number descriptor is negative\n" ) );
      }
      if( errno == ENOMEM ) {
        GSocket_Debug( ( "Not enough memory\n" ) );
      }
      m_error = GSOCK_TIMEDOUT;
      return GSOCK_TIMEDOUT;
    }
  }
  return GSOCK_NOERROR;
}

GSocketError GSocket::Output_Timeout() {
  struct timeval tv;
  fd_set writefds;
  int ret;
  tv.tv_sec  = ( m_timeout / 1000 );
  tv.tv_usec = ( m_timeout % 1000 ) * 1000;
  GSocket_Debug( ( "m_non_blocking has: %d\n", ( int )m_non_blocking ) );
  if( !m_non_blocking ) {
    wxFD_ZERO( &writefds );
    wxFD_SET( m_fd, &writefds );
    ret = select( m_fd + 1, NULL, &writefds, NULL, &tv );
    if( ret == 0 ) {
      GSocket_Debug( ( "GSocket_Output_Timeout, select returned 0\n" ) );
      m_error = GSOCK_TIMEDOUT;
      return GSOCK_TIMEDOUT;
    }
    if( ret == -1 ) {
      GSocket_Debug( ( "GSocket_Output_Timeout, select returned -1\n" ) );
      if( errno == EBADF ) {
        GSocket_Debug( ( "Invalid file descriptor\n" ) );
      }
      if( errno == EINTR ) {
        GSocket_Debug( ( "A non blocked signal was caught\n" ) );
      }
      if( errno == EINVAL ) {
        GSocket_Debug( ( "The highest number descriptor is negative\n" ) );
      }
      if( errno == ENOMEM ) {
        GSocket_Debug( ( "Not enough memory\n" ) );
      }
      m_error = GSOCK_TIMEDOUT;
      return GSOCK_TIMEDOUT;
    }
    if( ! wxFD_ISSET( m_fd, &writefds ) ) {
      GSocket_Debug( ( "GSocket_Output_Timeout is buggy!\n" ) );
    } else {
      GSocket_Debug( ( "GSocket_Output_Timeout seems correct\n" ) );
    }
  } else {
    GSocket_Debug( ( "GSocket_Output_Timeout, didn't try select!\n" ) );
  }
  return GSOCK_NOERROR;
}

int GSocket::Recv_Stream( char *buffer, int size ) {
  int ret;
  do {
    ret = recv( m_fd, buffer, size, GSOCKET_MSG_NOSIGNAL );
  } while( ret == -1 && errno == EINTR );
  return ret;
}

int GSocket::Recv_Dgram( char *buffer, int size ) {
  struct sockaddr from;
  WX_SOCKLEN_T fromlen = sizeof( from );
  int ret;
  GSocketError err;
  fromlen = sizeof( from );
  do {
    ret = recvfrom( m_fd, buffer, size, 0, &from, ( WX_SOCKLEN_T * ) &fromlen );
  } while( ret == -1 && errno == EINTR );
  if( ret == -1 ) {
    return -1;
  }
  if( !m_peer ) {
    m_peer = GAddress_new();
    if( !m_peer ) {
      m_error = GSOCK_MEMERR;
      return -1;
    }
  }
  err = _GAddress_translate_from( m_peer, &from, fromlen );
  if( err != GSOCK_NOERROR ) {
    GAddress_destroy( m_peer );
    m_peer  = NULL;
    m_error = err;
    return -1;
  }
  return ret;
}

int GSocket::Send_Stream( const char *buffer, int size ) {
  int ret;
  MASK_SIGNAL();
  do {
    ret = send( m_fd, ( char * )buffer, size, GSOCKET_MSG_NOSIGNAL );
  } while( ret == -1 && errno == EINTR );
  UNMASK_SIGNAL();
  return ret;
}

int GSocket::Send_Dgram( const char *buffer, int size ) {
  struct sockaddr *addr;
  int len, ret;
  GSocketError err;
  if( !m_peer ) {
    m_error = GSOCK_INVADDR;
    return -1;
  }
  err = _GAddress_translate_to( m_peer, &addr, &len );
  if( err != GSOCK_NOERROR ) {
    m_error = err;
    return -1;
  }
  MASK_SIGNAL();
  do {
    ret = sendto( m_fd, ( char * )buffer, size, 0, addr, len );
  } while( ret == -1 && errno == EINTR );
  UNMASK_SIGNAL();
  free( addr );
  return ret;
}

void GSocket::Detected_Read() {
  char c;
  if( m_fd == INVALID_SOCKET ) {
    return;
  }
  if( ( m_detected & GSOCK_LOST_FLAG ) != 0 ) {
    m_establishing = false;
    CALL_CALLBACK( this, GSOCK_LOST );
    Shutdown();
    return;
  }
  int num =  recv( m_fd, &c, 1, MSG_PEEK | GSOCKET_MSG_NOSIGNAL );
  if( num > 0 ) {
    CALL_CALLBACK( this, GSOCK_INPUT );
  } else {
    if( m_server && m_stream ) {
      CALL_CALLBACK( this, GSOCK_CONNECTION );
    } else if( num == 0 ) {
      if( m_stream ) {
        CALL_CALLBACK( this, GSOCK_LOST );
        Shutdown();
      } else {
        CALL_CALLBACK( this, GSOCK_INPUT );
      }
    } else {
      if( ( errno == EWOULDBLOCK ) || ( errno == EAGAIN ) || ( errno == EINTR ) ) {
        CALL_CALLBACK( this, GSOCK_INPUT );
      } else {
        CALL_CALLBACK( this, GSOCK_LOST );
        Shutdown();
      }
    }
  }
}

void GSocket::Detected_Write() {
  if( ( m_detected & GSOCK_LOST_FLAG ) != 0 ) {
    m_establishing = false;
    CALL_CALLBACK( this, GSOCK_LOST );
    Shutdown();
    return;
  }
  if( m_establishing && !m_server ) {
    int error;
    SOCKOPTLEN_T len = sizeof( error );
    m_establishing = false;
    getsockopt( m_fd, SOL_SOCKET, SO_ERROR, ( char* )&error, &len );
    if( error ) {
      CALL_CALLBACK( this, GSOCK_LOST );
      Shutdown();
    } else {
      CALL_CALLBACK( this, GSOCK_CONNECTION );
      CALL_CALLBACK( this, GSOCK_OUTPUT );
    }
  } else {
    CALL_CALLBACK( this, GSOCK_OUTPUT );
  }
}

GSocket *GSocket_new( void ) {
  GSocket *newsocket = new GSocket();
  if( newsocket->IsOk() ) {
    return newsocket;
  }
  delete newsocket;
  return NULL;
}

#define CHECK_ADDRESS(address, family)                              \
  {                                                                   \
    if (address->m_family == GSOCK_NOFAMILY)                          \
      if (_GAddress_Init_##family(address) != GSOCK_NOERROR)          \
        return address->m_error;                                      \
    if (address->m_family != GSOCK_##family)                          \
    {                                                                 \
      address->m_error = GSOCK_INVADDR;                               \
      return GSOCK_INVADDR;                                           \
    }                                                                 \
  }

#define CHECK_ADDRESS_RETVAL(address, family, retval)               \
  {                                                                   \
    if (address->m_family == GSOCK_NOFAMILY)                          \
      if (_GAddress_Init_##family(address) != GSOCK_NOERROR)          \
        return retval;                                                \
    if (address->m_family != GSOCK_##family)                          \
    {                                                                 \
      address->m_error = GSOCK_INVADDR;                               \
      return retval;                                                  \
    }                                                                 \
  }


GAddress *GAddress_new( void ) {
  GAddress *address;
  if( ( address = ( GAddress * ) malloc( sizeof( GAddress ) ) ) == NULL ) {
    return NULL;
  }
  address->m_family  = GSOCK_NOFAMILY;
  address->m_addr    = NULL;
  address->m_len     = 0;
  return address;
}

GAddress *GAddress_copy( GAddress *address ) {
  GAddress *addr2;
  assert( address != NULL );
  if( ( addr2 = ( GAddress * ) malloc( sizeof( GAddress ) ) ) == NULL ) {
    return NULL;
  }
  memcpy( addr2, address, sizeof( GAddress ) );
  if( address->m_addr && address->m_len > 0 ) {
    addr2->m_addr = ( struct sockaddr * )malloc( addr2->m_len );
    if( addr2->m_addr == NULL ) {
      free( addr2 );
      return NULL;
    }
    memcpy( addr2->m_addr, address->m_addr, addr2->m_len );
  }
  return addr2;
}

void GAddress_destroy( GAddress *address ) {
  assert( address != NULL );
  if( address->m_addr ) {
    free( address->m_addr );
  }
  free( address );
}

void GAddress_SetFamily( GAddress *address, GAddressType type ) {
  assert( address != NULL );
  address->m_family = type;
}

GAddressType GAddress_GetFamily( GAddress *address ) {
  assert( address != NULL );
  return address->m_family;
}

GSocketError _GAddress_translate_from( GAddress *address, struct sockaddr *addr, int len ) {
  address->m_realfamily = addr->sa_family;
  switch( addr->sa_family ) {
    case AF_INET:
      address->m_family = GSOCK_INET;
      break;
    case AF_UNIX:
      address->m_family = GSOCK_UNIX;
      break;
    case AF_INET6:
      address->m_family = GSOCK_INET6;
      break;
    default: {
      address->m_error = GSOCK_INVOP;
      return GSOCK_INVOP;
    }
  }
  if( address->m_addr ) {
    free( address->m_addr );
  }
  address->m_len  = len;
  address->m_addr = ( struct sockaddr * )malloc( len );
  if( address->m_addr == NULL ) {
    address->m_error = GSOCK_MEMERR;
    return GSOCK_MEMERR;
  }
  memcpy( address->m_addr, addr, len );
  return GSOCK_NOERROR;
}

GSocketError _GAddress_translate_to( GAddress *address, struct sockaddr **addr, int *len ) {
  if( !address->m_addr ) {
    address->m_error = GSOCK_INVADDR;
    return GSOCK_INVADDR;
  }
  *len = address->m_len;
  *addr = ( struct sockaddr * )malloc( address->m_len );
  if( *addr == NULL ) {
    address->m_error = GSOCK_MEMERR;
    return GSOCK_MEMERR;
  }
  memcpy( *addr, address->m_addr, address->m_len );
  return GSOCK_NOERROR;
}

GSocketError _GAddress_Init_INET( GAddress *address ) {
  address->m_len  = sizeof( struct sockaddr_in );
  address->m_addr = ( struct sockaddr * ) malloc( address->m_len );
  if( address->m_addr == NULL ) {
    address->m_error = GSOCK_MEMERR;
    return GSOCK_MEMERR;
  }
  address->m_family = GSOCK_INET;
  address->m_realfamily = PF_INET;
  ( ( struct sockaddr_in * )address->m_addr )->sin_family = AF_INET;
  ( ( struct sockaddr_in * )address->m_addr )->sin_addr.s_addr = INADDR_ANY;
  return GSOCK_NOERROR;
}

GSocketError GAddress_INET_SetHostName( GAddress *address, const char *hostname ) {
  struct hostent *he;
  struct in_addr *addr;
  assert( address != NULL );
  CHECK_ADDRESS( address, INET );
  addr = &( ( ( struct sockaddr_in * )address->m_addr )->sin_addr );
  if( inet_aton( hostname, addr ) == 0 ) {
    struct in_addr *array_addr;
    struct hostent h;
    char buffer[1024];
    int err;
    he = wxGethostbyname_r( hostname, &h, ( void* )&buffer, sizeof( buffer ), &err );
    if( he == NULL ) {
      addr->s_addr = INADDR_NONE;
      address->m_error = GSOCK_NOHOST;
      return GSOCK_NOHOST;
    }
    array_addr = ( struct in_addr * ) * ( he->h_addr_list );
    addr->s_addr = array_addr[0].s_addr;
  }
  return GSOCK_NOERROR;
}

GSocketError GAddress_INET_SetAnyAddress( GAddress *address ) {
  return GAddress_INET_SetHostAddress( address, INADDR_ANY );
}

GSocketError GAddress_INET_SetHostAddress( GAddress *address, unsigned long hostaddr ) {
  struct in_addr *addr;
  assert( address != NULL );
  CHECK_ADDRESS( address, INET );
  addr = &( ( ( struct sockaddr_in * )address->m_addr )->sin_addr );
  addr->s_addr = htonl( hostaddr );
  return GSOCK_NOERROR;
}

GSocketError GAddress_INET_SetPortName( GAddress *address, const char *port, const char *protocol ) {
  struct servent *se;
  struct sockaddr_in *addr;
  assert( address != NULL );
  CHECK_ADDRESS( address, INET );
  if( !port ) {
    address->m_error = GSOCK_INVPORT;
    return GSOCK_INVPORT;
  }
  char buffer[1024];
  struct servent serv;
  se = wxGetservbyname_r( port, protocol, &serv, ( void* )&buffer, sizeof( buffer ) );
  if( !se ) {
    if( isdigit( ( int )port[0] ) ) {
      int port_int;
      port_int = atoi( port );
      addr = ( struct sockaddr_in * )address->m_addr;
      addr->sin_port = htons( port_int );
      return GSOCK_NOERROR;
    }
    address->m_error = GSOCK_INVPORT;
    return GSOCK_INVPORT;
  }
  addr = ( struct sockaddr_in * )address->m_addr;
  addr->sin_port = se->s_port;
  return GSOCK_NOERROR;
}

GSocketError GAddress_INET_SetPort( GAddress *address, unsigned short port ) {
  struct sockaddr_in *addr;
  assert( address != NULL );
  CHECK_ADDRESS( address, INET );
  addr = ( struct sockaddr_in * )address->m_addr;
  addr->sin_port = htons( port );
  return GSOCK_NOERROR;
}

GSocketError GAddress_INET_GetHostName( GAddress *address, char *hostname, size_t sbuf ) {
  struct hostent *he;
  char *addr_buf;
  struct sockaddr_in *addr;
  assert( address != NULL );
  CHECK_ADDRESS( address, INET );
  addr = ( struct sockaddr_in * )address->m_addr;
  addr_buf = ( char * ) & ( addr->sin_addr );
  struct hostent temphost;
  char buffer[1024];
  int err;
  he = wxGethostbyaddr_r( addr_buf, sizeof( addr->sin_addr ), AF_INET, &temphost, ( void* )&buffer, sizeof( buffer ), &err );
  if( he == NULL ) {
    address->m_error = GSOCK_NOHOST;
    return GSOCK_NOHOST;
  }
  strncpy( hostname, he->h_name, sbuf );
  return GSOCK_NOERROR;
}

unsigned long GAddress_INET_GetHostAddress( GAddress *address ) {
  struct sockaddr_in *addr;
  assert( address != NULL );
  CHECK_ADDRESS_RETVAL( address, INET, 0 );
  addr = ( struct sockaddr_in * )address->m_addr;
  return ntohl( addr->sin_addr.s_addr );
}

unsigned short GAddress_INET_GetPort( GAddress *address ) {
  struct sockaddr_in *addr;
  assert( address != NULL );
  CHECK_ADDRESS_RETVAL( address, INET, 0 );
  addr = ( struct sockaddr_in * )address->m_addr;
  return ntohs( addr->sin_port );
}

GSocketError _GAddress_Init_UNIX( GAddress *address ) {
  address->m_len  = sizeof( struct sockaddr_un );
  address->m_addr = ( struct sockaddr * )malloc( address->m_len );
  if( address->m_addr == NULL ) {
    address->m_error = GSOCK_MEMERR;
    return GSOCK_MEMERR;
  }
  address->m_family = GSOCK_UNIX;
  address->m_realfamily = PF_UNIX;
  ( ( struct sockaddr_un * )address->m_addr )->sun_family = AF_UNIX;
  ( ( struct sockaddr_un * )address->m_addr )->sun_path[0] = 0;
  return GSOCK_NOERROR;
}

#define UNIX_SOCK_PATHLEN (sizeof(addr->sun_path)/sizeof(addr->sun_path[0]))

GSocketError GAddress_UNIX_SetPath( GAddress *address, const char *path ) {
  struct sockaddr_un *addr;
  assert( address != NULL );
  CHECK_ADDRESS( address, UNIX );
  addr = ( ( struct sockaddr_un * )address->m_addr );
  strncpy( addr->sun_path, path, UNIX_SOCK_PATHLEN );
  addr->sun_path[UNIX_SOCK_PATHLEN - 1] = '\0';
  return GSOCK_NOERROR;
}

GSocketError GAddress_UNIX_GetPath( GAddress *address, char *path, size_t sbuf ) {
  struct sockaddr_un *addr;
  assert( address != NULL );
  CHECK_ADDRESS( address, UNIX );
  addr = ( struct sockaddr_un * )address->m_addr;
  strncpy( path, addr->sun_path, sbuf );
  return GSOCK_NOERROR;
}
